using SharpGraphT.Sharp.Sharp.Util;
using SharpGraphT.Sharp.System.Collections.Generic;
using SharpGraphT.Sharp.Util;

namespace SharpGraphT.Graph.Specifics;

public class FastLookupUndirectedSpecifics<TV, TE> : UndirectedSpecifics<TV, TE>
    where TE : class, new() {
    private static readonly long SerialVersionUid = 225772727571597846L;

    protected IDictionary<(TV, TV), ISet<TE>> touchingVerticesToEdgeMap;

    public FastLookupUndirectedSpecifics(IGraph<TV, TE> graph,
        IDictionary<TV, UndirectedEdgeContainer<TV, TE>> vertexMap,
        IDictionary<(TV, TV), ISet<TE>> touchingVerticesToEdgeMap,
        IEdgeSetFactory<TV, TE> edgeSetFactory) : base(graph, vertexMap,
        edgeSetFactory) =>
        this.touchingVerticesToEdgeMap =
            Objects.RequireNonNull(touchingVerticesToEdgeMap);


    public override IReadOnlySet<TE> GetAllEdges(TV sourceVertex,
        TV targetVertex) {
        if (!(graph.ContainsVertex(sourceVertex) &&
                graph.ContainsVertex(targetVertex))) {
            return null;
        }

        if (!touchingVerticesToEdgeMap.TryGetValueUnordered(
                (sourceVertex, targetVertex), out var edges)) {
            return Collections.GetEmptyReadOnlySet<TE>();
        }

        var edgeSet = edgeSetFactory.CreateEdgeSet(sourceVertex);
        edgeSet.AddRange(edges);
        return edgeSet;
    }

    public override TE GetEdge(TV sourceVertex, TV targetVertex) =>
        !touchingVerticesToEdgeMap.TryGetValueUnordered(
            (sourceVertex, targetVertex), out var edges) || !edges.Any()
            ? null
            : edges.First();

    public override bool AddEdgeToTouchingVertices(TV sourceVertex,
        TV targetVertex, TE e) {
        if (!base.AddEdgeToTouchingVertices(sourceVertex, targetVertex, e)) {
            return false;
        }

        AddToIndex(sourceVertex, targetVertex, e);
        return true;
    }

    public override bool AddEdgeToTouchingVerticesIfAbsent(TV sourceVertex,
        TV targetVertex, TE e) =>
        GetEdge(sourceVertex, targetVertex) == null &&
        AddEdgeToTouchingVertices(sourceVertex, targetVertex, e);

    public override TE CreateEdgeToTouchingVerticesIfAbsent(TV sourceVertex,
        TV targetVertex, Func<TE> edgeSupplier) {
        if (GetEdge(sourceVertex, targetVertex) != null) {
            return null;
        }

        var e = edgeSupplier();
        AddEdgeToTouchingVertices(sourceVertex, targetVertex, e);
        return e;
    }

    public override void RemoveEdgeFromTouchingVertices(TV sourceVertex,
        TV targetVertex, TE e) {
        base.RemoveEdgeFromTouchingVertices(sourceVertex, targetVertex, e);
        RemoveFromIndex(sourceVertex, targetVertex, e);
    }

    protected void AddToIndex(TV sourceVertex, TV targetVertex, TE e) {
        var pair = (sourceVertex, targetVertex);
        if (touchingVerticesToEdgeMap.TryGetValueUnordered(pair,
                out var edgeSet)) {
            edgeSet.Add(e);
        } else {
            edgeSet = edgeSetFactory.CreateEdgeSet(sourceVertex);
            edgeSet.Add(e);
            touchingVerticesToEdgeMap.SetValueUnordered(pair, edgeSet);
        }
    }

    protected void RemoveFromIndex(TV sourceVertex, TV targetVertex, TE e) {
        if (!touchingVerticesToEdgeMap.TryGetValueUnordered(
                (sourceVertex, targetVertex), out var edgeSet)) {
            return;
        }

        edgeSet.Remove(e);
        if (!edgeSet.Any()) {
            touchingVerticesToEdgeMap.RemoveUnordered((sourceVertex,
                targetVertex));
        }
    }
}